package no.rmz.chez.evaluators;

import java.util.Map.Entry;

import no.rmz.chez.chess.board.ChessBoard;
import no.rmz.chez.chess.board.Color;
import no.rmz.chez.chess.board.Coord;
import no.rmz.chez.chess.pieces.Bishop;
import no.rmz.chez.chess.pieces.ChessPiece;
import no.rmz.chez.chess.pieces.King;
import no.rmz.chez.chess.pieces.Knight;
import no.rmz.chez.chess.pieces.Pawn;
import no.rmz.chez.chess.pieces.Queen;
import no.rmz.chez.chess.pieces.Rook;


/**
 * A simple evaluator for a board.  We're assuming
 * that the position we're evaluating is -not-
 * a terminal one, henc we ill never
 * produce infinite values.
 */
public final class CountingEvaluator extends BoardEvaluator {

    /**
     * Private creator for singelton class.
     *
     */
    private CountingEvaluator() {
    }

    /**
     * Implementing singelton pattern.
     */
    private  static BoardEvaluator instance = null;

    /**
     * Accessor for instance.
     * @return get this board instance.
     */
    public static BoardEvaluator getInstance() {
        if (instance ==  null) {
            instance = new CountingEvaluator();
        }
        return instance;
    }


    /**
     * The value for a pawn.
     */
    public static final int  PAWN_VAL = 1;

    /**
     * The value for a rook.
     */
    public static final int ROOK_VAL = 2;

    /**
     * The value for a rook.
     */
    public static final int KNIGHT_VAL = 3;

    /**
     * The value of a knight.
     */
    public static final int BISHOP_VAL = 4;

    /**
     * The value of a queen.
     */
    public static final   int QUEEN_VAL = 5;

    /**
     * The value of a king.
     */
    public static final   int KING_VAL = 1;

    /**
     * Assign a double value to each type of piece,
     * use that to evaluate the strength of a board
     * XXX. This is an -extremely- crude method of
     * strength evaluation, almost impossibly stupid.
     * @param p the piece go evaluate
     * @return the worth of the piece
     */
    private  double getValue(final ChessPiece p) {
        Class pc = p.getClass();
        if (pc == Pawn.class) {
            return PAWN_VAL;
        } else if (pc == Rook.class) {
            return ROOK_VAL;
        } else if (pc == Knight.class) {
            return KNIGHT_VAL;
        } else if (pc == Bishop.class) {
            return BISHOP_VAL;
        } else if (pc == Queen.class) {
            return QUEEN_VAL;
        } else if (pc == King.class) {
            return KING_VAL;
        }
        throw new RuntimeException("Unknown chesspiece " + pc);
    }


    /**
     * Evaluate a board as seen from the perspective of a color.
     * @param b the board.
     * @param c the color.
     * @return the combined goodness.
     */
    @Override
    public double evaluate(final ChessBoard b, final Color c) {
        int whites = 0;
        int blacks = 0;

        for (final Entry<Coord, ChessPiece> e: b.getPiecesMap().entrySet()) {
            final ChessPiece pie = e.getValue();
            final double     val = getValue(pie);
            if (pie.getColor() == Color.WHITE) {
                whites += val;
            } else {
                blacks += val;
            }
        }

        assert (whites >= 0);
        assert (blacks >= 0);

        double goodness = 0;
        if (c == Color.BLACK) {
            goodness = calculateGoodness(blacks, whites);
        } else {
            goodness = calculateGoodness(whites, blacks);
        }
        return goodness;
    }

    /**
     * Given the relative strenghts of the the different colors,
     * calculate the combined goodness of the combination.
     *
     * @param a the strength of one of the colors.
     * @param b the strength of the other color.
     * @return the combined goodness.
     */
    private static double calculateGoodness(final double a, final double b) {
        double  result;
        if (a == 0  && b > 0) {
            result =  NEG_INFTY;
        } else if (b == 0 &&  a > 0) {
            result = POS_INFTY;
        } else {
            result = a - b;
        }
        return result;
    }

}
